/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.stetho.inspector.helper;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import java.nio.channels.NotYetConnectedException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.facebook.stetho.common.LogRedirector;
import com.facebook.stetho.common.Util;
import com.facebook.stetho.inspector.jsonrpc.DisconnectReceiver;
import com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;
import com.facebook.stetho.inspector.jsonrpc.PendingRequestCallback;
/**
* Interface glue that allows a particular domain to manage the enabled peers. The way the
* WebKit inspector protocol works is that each functionality domain has an enable/disable JSON-RPC
* method call which alerts the server (that's us) that we can now begin sending local events
* to the peer to have them appear in the inspector UI. This class simplifies managing those
* enabled peers for each functionality domain.
*/
public class ChromePeerManager {
private static final String TAG = "ChromePeerManager";
/**
* Set of registered peers, mapped to the disconnect receiver for automatic unregistration
* purposes.
*/
@GuardedBy("this")
private final Map<JsonRpcPeer, DisconnectReceiver> mReceivingPeers = new HashMap<>();
/**
* This should be set to null anytime mReceivingPeers is changed. It should always be
* retrieved by calling getReceivingPeersSnapshot().
*/
@GuardedBy("this")
private JsonRpcPeer[] mReceivingPeersSnapshot;
@GuardedBy("this")
private PeerRegistrationListener mListener;
public ChromePeerManager() {
}
/**
* Set a listener which can receive notifications of unique registration event (see
* {@link #addPeer} and {@link #removePeer}).
*
* @param listener
*/
public synchronized void setListener(PeerRegistrationListener listener) {
mListener = listener;
}
/**
* Register a new peer, adding them to an internal list of receivers.
*
* @param peer
* @return True if this is a newly registered peer; false if it was already registered.
*/
public synchronized boolean addPeer(JsonRpcPeer peer) {
if (mReceivingPeers.containsKey(peer)) {
return false;
}
DisconnectReceiver disconnectReceiver = new UnregisterOnDisconnect(peer);
peer.registerDisconnectReceiver(disconnectReceiver);
mReceivingPeers.put(peer, disconnectReceiver);
mReceivingPeersSnapshot = null;
if (mListener != null) {
mListener.onPeerRegistered(peer);
}
return true;
}
/**
* Unregister an existing peer.
*
* @param peer
*/
public synchronized void removePeer(JsonRpcPeer peer) {
if (mReceivingPeers.remove(peer) != null) {
mReceivingPeersSnapshot = null;
if (mListener != null) {
mListener.onPeerUnregistered(peer);
}
}
}
public synchronized boolean hasRegisteredPeers() {
return !mReceivingPeers.isEmpty();
}
private synchronized JsonRpcPeer[] getReceivingPeersSnapshot() {
if (mReceivingPeersSnapshot == null) {
mReceivingPeersSnapshot = mReceivingPeers.keySet().toArray(
new JsonRpcPeer[mReceivingPeers.size()]);
}
return mReceivingPeersSnapshot;
}
public void sendNotificationToPeers(String method,
Object params) {
sendMessageToPeers(method, params, null /* callback */);
}
public void invokeMethodOnPeers(String method,
Object params,
PendingRequestCallback callback) {
Util.throwIfNull(callback);
sendMessageToPeers(method, params, callback);
}
private void sendMessageToPeers(String method,
Object params,
@Nullable PendingRequestCallback callback) {
JsonRpcPeer[] peers = getReceivingPeersSnapshot();
for (JsonRpcPeer peer : peers) {
try {
peer.invokeMethod(method, params, callback);
} catch (NotYetConnectedException e) {
LogRedirector.e(TAG, "Error delivering data to Chrome", e);
}
}
}
private class UnregisterOnDisconnect implements DisconnectReceiver {
private final JsonRpcPeer mPeer;
public UnregisterOnDisconnect(JsonRpcPeer peer) {
mPeer = peer;
}
@Override
public void onDisconnect() {
removePeer(mPeer);
}
}
}